﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using cmdLineParserModule;
using cmdLineParserModule.HelperClasses;
using System.Text.RegularExpressions;

namespace cmdLineParserModule
{
    /// <summary>
    /// Main abstract attribute class containing common methods for checking and evaluating
    /// choices from command line. This class is used for marking properties that are ought 
    /// to be filled with value/set to null when choice is not entered.
    /// </summary>
    public abstract class optionAttribute : Attribute
    {
        /// <summary>
        /// Choice command name (i.e. "d" for usage "-d", "dest" for "--dest")
        /// </summary>
        public string command;

        /// <summary>
        /// Alternative choice commands
        /// </summary>
        public string[] alternatives = new string[0];

        /// <summary>
        /// Description of choice printed for user in method parser.printUsage()
        /// </summary>
        public string description;
        
        /// <summary>
        /// Defines if choice is required (must be entered by user)
        /// </summary>
        public bool choiceRequired = true;

        /// <summary>
        /// Creates usage text printed in parser.printUsage(), i.e. "[ -d [ string ] ]"
        /// </summary>
        /// <param name="command">Name of command or alternative command</param>
        /// <returns>Usage text in printable format</returns>
        public abstract string getUsageText(string command);

        /// <summary>
        /// Method used in derived classes for checking correctness of values entered.
        /// </summary>
        protected virtual void customCheck() { }
        
        /// <summary>
        /// Reference to property of argument class
        /// </summary>
        internal FieldInfo field;

        /// <summary>
        /// Fills field with appropriate value (is never used by user)
        /// </summary>
        /// <param name="argumentClass">Argument class contating fields to fill</param>
        /// <param name="choiceToken">Token containing choice or null</param>
        /// <param name="parameterToken">Token contating parameter of choice or null</param>
        internal abstract void parse(object argumentClass, cmdLineToken choiceToken, cmdLineToken parameterToken);

        /// <summary>
        /// Gets full name of field for better understanding (used mostly when throwing an exception)
        /// </summary>
        /// <returns>Full name of assigned field</returns>
        internal string fieldFullName()
        {
            if (field == null)
            { return null; }
            else
            { return string.Format("{0}.{1}", field.DeclaringType.Name, field.Name); }
        }


        /// <summary>
        /// Method called before parsing, can't be overriden, for custom checking please override method customCheck()
        /// </summary>
        internal void check()
        {
            checkCommand();
            checkFieldType();

            customCheck();
        }

        /// <summary>
        /// Method used to identify the only type associated with option class
        /// </summary>
        /// <returns>Expected (therefore accepted) type of property marked with option class</returns>
        internal abstract Type typeAccepted();

        /// <summary>
        /// Checks, if type of field associated with optionAttribute class fits
        /// </summary>
        private void checkFieldType()
        {
            Type expectedType = this.typeAccepted();
            Type fieldType = field.FieldType;

            if (!expectedType.Equals(fieldType))
                throw new WrongDefinitionException(string.Format("Wrong type at property {0} ({1} found, {2} expected)", fieldFullName(), fieldType.Name, expectedType.Name));
        }

        /// <summary>
        /// Checks if command is entered in correct format
        /// </summary>
        private void checkCommand()
        {
            if (this.command == null)
                throw new WrongDefinitionException(string.Format("Property {0} hasn't command defined.", fieldFullName()));

            //if (new Regex("[a-zA-Z]").Match(this.command).Length != this.command.Length)
            if (!ChoiceTextManager.fitsChoicePattern(this.command))
                throw new WrongDefinitionException(string.Format("Command can constist of [a-z],[A-Z] only (found {0} at property {1}).", this.command, fieldFullName()));

            foreach (string alternative in alternatives)
            {
                if (!ChoiceTextManager.fitsChoicePattern(alternative))
                    throw new WrongDefinitionException(string.Format("Command can constist of [a-z],[A-Z] only (found {0} at property {1}).", alternative, fieldFullName()));
            }
        }
    }

}
